﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace JustNotJewelryMain
{
    
    class Engine
    {
        private List<GameObject> allObjects;
        private List<GameObject> movableObjects;
        private List<GameObject> staticObjects;
        private List<GameObject> infoObjects;
        private List<GameObject> dynamicObjects;

        public Renderer Renderer { get; private set; }
        private Input movement;
        private Random randomNumberGenerator = new Random();
        private FallingBlock fallingBlock;
        private FallingBlock bigBomb;

        private int gameSpeed;
        private bool hasGameEnded = false;
        private int score = 0;

        public Engine(Renderer renderer, int gameSpeed, Input movement)
        {
            this.Renderer = renderer;
            this.movement = movement;

            this.staticObjects = new List<GameObject>();
            this.movableObjects = new List<GameObject>();
            this.allObjects = new List<GameObject>();
            this.infoObjects = new List<GameObject>();
            this.dynamicObjects = new List<GameObject>();
            //this.bigBomb = new ExplodingBlock(Coordinates ());
            this.gameSpeed = gameSpeed;

            char[,] fallingItemBody = new char[,] { { '#' } };
            this.fallingBlock = new FallingBlock(new Coordinates(6, (this.Renderer.GridMatrix.GetLength(1) / 2) - 1), new Coordinates(1, 0), fallingItemBody);
            this.AddObject(fallingBlock);
        }

        /// Add objects in the object lists
        private void AddInfoObjects(GameObject obj)
        {
            infoObjects.Add(obj);
        }
        private void AddMovableObject(GameObject obj)
        {
            movableObjects.Add(obj);
        }
        private void AddFallenStaticObject(GameObject obj)
        {
            this.staticObjects.Add(obj);
        }

        public void AddObject(GameObject obj)
        {
            if (obj is MovableObject)
            {
                this.AddMovableObject(obj);
            }
            if (obj is StaticObject)
            {
                this.AddFallenStaticObject(obj);
            }
            if (!obj.isDestroyed)
            {
                allObjects.Add(obj);
            }
            if (!(obj is Text) && !(obj is InfoWall) && !(obj is TimeText))
            {
                allObjects.Add(obj);
            }
            else
            {
                AddInfoObjects(obj);
            }
        }

        private bool HasLanded(GameObject obj)
        {
            return obj.TopLeft.Row + obj.GetImage().GetLength(1) >= this.Renderer.GridMatrix.GetLength(1) ||
            !this.Renderer.GridMatrix[obj.TopLeft.Row + obj.GetImage().GetLength(1), obj.TopLeft.Col].Equals(' ');
        }

        private bool HasGameEnded(GameObject obj)
        {
            return obj.TopLeft.Row == 5 &&
                this.Renderer.GridMatrix[obj.TopLeft.Row + obj.GetImage().GetLength(1), obj.TopLeft.Col] != ' ';
        }

        private void OnGameOver()
        {
            hasGameEnded = true;
            Console.ForegroundColor = ConsoleColor.Red;
            Console.SetCursorPosition(40, 10);

            Console.Write("GAME OVER");
        }

        //Filling the matrix
        private void FillFallenObjects(GameObject obj)
        {
            this.dynamicObjects.Add(obj);
        }

        // Calculating explosion        
        private void ExplosionCalculation(GameObject currentObject)
        {
            if (this.dynamicObjects.Count > 1)
            {
                foreach (var item in this.dynamicObjects)
                {
                    if (item.TopLeft.Col == currentObject.TopLeft.Col &&
                        (item.TopLeft.Row == currentObject.TopLeft.Row + 1) &&
                        (item.ObjectColor == currentObject.ObjectColor))
                    {
                        item.isDestroyed = true;
                        currentObject.isDestroyed = true;
                        GetUpdatedScore();
                        score++;
                    }

                    if (item.TopLeft.Col == currentObject.TopLeft.Col + 1 &&
                        (item.TopLeft.Row == currentObject.TopLeft.Row) &&
                        (item.ObjectColor == currentObject.ObjectColor))
                    {
                        item.isDestroyed = true;
                        currentObject.isDestroyed = true;
                        GetUpdatedScore();
                        score++;
                    }

                    if (item.TopLeft.Col == currentObject.TopLeft.Col - 1 &&
                        (item.TopLeft.Row == currentObject.TopLeft.Row) &&
                        (item.ObjectColor == currentObject.ObjectColor))
                    {
                        item.isDestroyed = true;
                        currentObject.isDestroyed = true;
                        GetUpdatedScore();
                        score++;
                    }
                }
            }
        }

        //GettinHarder
        private void GetHarder()
        {
            foreach (var item in dynamicObjects)
            {
                item.TopLeft = new Coordinates(item.TopLeft.Row - 1, item.TopLeft.Col);
            }
            AddOneMoreLine();
            TranferCalculatedMatrix();
        }
        //checkabove
        private void CheckAbove(GameObject objDestroyed)
        {
            int row = objDestroyed.TopLeft.Row;
            int col = objDestroyed.TopLeft.Col;

            for (int i = 0; i < this.dynamicObjects.Count; i++)
            {
                if (!dynamicObjects[i].isDestroyed &&
                    (dynamicObjects[i].TopLeft.Row + 1 == objDestroyed.TopLeft.Row) &&
                    (dynamicObjects[i].TopLeft.Col == objDestroyed.TopLeft.Col))
                {
                    dynamicObjects[i].TopLeft = new Coordinates(row, col);
                }
            }
        }

        //falling down
        private void FallDown()
        {
            for (int i = 0; i < this.dynamicObjects.Count; i++)
            {
                if (this.dynamicObjects[i].isDestroyed)
                {
                    CheckAbove(dynamicObjects[i]);
                    this.dynamicObjects.Remove(this.dynamicObjects[i]);
                }
            }
        }

        //Transfering
        private void TranferCalculatedMatrix()
        {
            FallDown();

            for (int i = 0; i < this.dynamicObjects.Count; i++)
            {
                if (!this.dynamicObjects[i].isDestroyed)
                {
                    this.allObjects.Add(this.dynamicObjects[i]);
                }
            }
        }
        //clear Garbage
        private void ClearGarbage()
        {
            for (int i = 0; i < this.allObjects.Count; i++)
            {
                if (this.allObjects[i].isDestroyed)
                {
                    this.allObjects.Remove(allObjects[i]);
                }
            }
        }
        private void OnExplosionActions(GameObject obj)
        {
            //filing the matrixes                            
            FillFallenObjects(obj);

            //callculate explosion
            ExplosionCalculation(obj);

            //falling down           
            FallDown();

            ClearGarbage();

            //transferingData
            TranferCalculatedMatrix();
        }

        ///Restart the falling item position
        private void InitializeFallingItemAtStartPosition(GameObject obj)
        {
            int visibleCols = this.Renderer.GridMatrix.GetLength(1);
            int startRow = 5;
            int startCol = (visibleCols / 2) - 1;
            obj.TopLeft = new Coordinates(startRow, startCol);

            ConsoleColor currentRandomColor = (ConsoleColor)randomNumberGenerator.Next(0, 14);
            if ((currentRandomColor != ConsoleColor.Black) &&
                (currentRandomColor != ConsoleColor.White))
            {
                obj.ObjectColor = currentRandomColor;
            }
        }

        //Initialize getHardLine
        private void AddOneMoreLine()
        {
            int row = this.Renderer.GridMatrix.GetLength(0) - 1;
            int col = this.Renderer.GridMatrix.GetLength(1) - 1;

            for (int i = col; i > 0; i--)
            {                
                StaticObject randomElement = new StaticObject();
                randomElement.TopLeft = new Coordinates(row, i);
                ConsoleColor currentRandomColor = (ConsoleColor)randomNumberGenerator.Next(0, 14);
                if ((currentRandomColor != ConsoleColor.Black) &&
                    (currentRandomColor != ConsoleColor.White))
                {
                    randomElement.ObjectColor = currentRandomColor;
                }
                this.dynamicObjects.Add(randomElement);
            }
        }

        /// Events implementation (move right, left, change color with the spacebar, speed up falling item, change manualy game speed)
        private bool IsInMatrixRangeOnMove()
        {
            return this.fallingBlock.TopLeft.Col - 1 >= 0 &&
                this.fallingBlock.TopLeft.Col + 1 < this.Renderer.GridMatrix.GetLength(0);
        }

        public void MoveLeft()
        {
            if (IsInMatrixRangeOnMove())
            {
                this.fallingBlock.MoveLeft();
            }
        }
        public void MoveRight()
        {
            if (IsInMatrixRangeOnMove())
            {
                this.fallingBlock.MoveRight();
            }
        }
        public void ChangeFallingItemColor()
        {
            this.fallingBlock.ChangeColor((ConsoleColor)randomNumberGenerator.Next(0, 14));
        }

        public void ManualIncreaseGameSpeed()
        {
            int fastestSpeed = (int)LevelSpeed.Mental;
            if (this.gameSpeed >= fastestSpeed)
            {
                LevelSpeed nextSpeed = (LevelSpeed)(this.gameSpeed - 30);
                this.gameSpeed = (int)nextSpeed;
            }
            else
            {
                throw new LevelSpeedException("U mad bro? Slow down!");
            }
        }

        public void ManualDecreaseGameSpeed()
        {
            int slowestSpeed = (int)LevelSpeed.GrandmaPlay;
            if (this.gameSpeed <= slowestSpeed)
            {
                LevelSpeed nextSpeed = (LevelSpeed)(this.gameSpeed + 30);
                this.gameSpeed = (int)nextSpeed;
            }
            else
            {
                throw new LevelSpeedException("Schneller, Schneller!");
            }
        }

        public void SpeedUpFallingItem()
        {
            if (fallingBlock.TopLeft.Row + 1 < this.Renderer.GridMatrix.GetLength(0))
            {
                fallingBlock.SpeedUp();
            }
        }

        /// Update the score after destroying elements
        private void GetUpdatedScore()
        {
            foreach (var obj in infoObjects)
            {
                if (obj is Scores)
                {
                    obj.Update();
                }
            }
        }

        /// Timer - Update the played time
        private void GetUpdatedTimePlayed()
        {
            foreach (var obj in infoObjects)
            {
                if (obj is TimeText)
                {
                    obj.Update();
                }
            }
        }

        /// Automaticly increase game speed on every 30 seconds
        private void IncreaseGameSpeed()
        {
            if (this.gameSpeed > 50)
            {
                if (TimeText.playedTime.Second == 30)
                {
                    this.gameSpeed -= 25;                    
                }
            }
        }

        public void Run()
        {
            while (true)
            {
                Renderer.ClearFieldGrid();

                IncreaseGameSpeed();
                if (this.score % 3 == 0 && this.score > 0)
                {
                    GetHarder();
                    //this.score = 0;
                }

                this.movement.ProcessInput();
                Thread.Sleep(this.gameSpeed);

                foreach (var obj in this.allObjects)
                {
                    this.Renderer.EnqueueItem(obj);
                }

                foreach (var obj in this.movableObjects)
                {
                    if (obj is FallingBlock)
                    {
                        if (this.HasLanded(obj))
                        {
                            GameObject currentStaticObject = new StaticObject(obj.TopLeft, obj.ObjectColor);

                            OnExplosionActions(currentStaticObject);

                            //InitializeFallingItemAtStartPosition(obj);
                            if (this.score % 3 == 0 && this.score > 0)
                            {
                                
                            }
                            obj.InitializeNewObject(this.Renderer, obj);

                            if (this.HasGameEnded(obj))
                            {
                                this.OnGameOver();
                            }
                        }
                    }
                    obj.Update();
                }

                GetUpdatedTimePlayed();

                Renderer.RenderAll(this.allObjects, this.infoObjects);

                if (hasGameEnded) { break; }
            }
        }
    }
}
